package com.sooth.micro.camunda.service;

import cn.hutool.core.util.StrUtil;
import com.dream.system.config.Page;
import com.sooth.common.core.utils.SpringUtils;
import com.sooth.common.satoken.utils.LoginHelper;
import com.sooth.micro.camunda.converter.BpmnBuilder;
import com.sooth.micro.camunda.converter.bean.extension.ExtBpmConstant;
import com.sooth.micro.camunda.converter.bean.extension.ProcessStatus;
import com.sooth.micro.camunda.view.ProcessDefinitionDto;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.camunda.bpm.engine.*;
import org.camunda.bpm.engine.history.*;
import org.camunda.bpm.engine.impl.RepositoryServiceImpl;
import org.camunda.bpm.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.camunda.bpm.engine.impl.pvm.process.ActivityImpl;
import org.camunda.bpm.engine.repository.Deployment;
import org.camunda.bpm.engine.repository.ProcessDefinition;
import org.camunda.bpm.engine.repository.ProcessDefinitionQuery;
import org.camunda.bpm.engine.spring.SpringProcessEngineConfiguration;
import org.camunda.bpm.engine.task.Task;
import org.camunda.bpm.engine.task.TaskQuery;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.camunda.bpm.model.bpmn.instance.EndEvent;
import org.camunda.bpm.model.bpmn.instance.FlowElement;
import org.camunda.bpm.model.bpmn.instance.Process;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.stereotype.Service;

import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.sooth.micro.camunda.service.ICamundaApiService.CAMUNDA_BPM_DATASOURCE;

@ConditionalOnBean(name = CAMUNDA_BPM_DATASOURCE)
@RequiredArgsConstructor
@Service
public class CamundaApiServiceImpl implements ICamundaApiService {
    protected final IdentityService identityService;
    private final RepositoryService repositoryService;
    private final RuntimeService runtimeService;
    private final TaskService taskService;
    private final HistoryService historyService;

    @PostConstruct
    public void init() {
        SpringProcessEngineConfiguration configuration = SpringUtils.getBean(SpringProcessEngineConfiguration.class);
        configuration.setEnforceHistoryTimeToLive(false);
    }

    @Override
    public Deployment deploy(String processId, String processName, String processJson, String versionTag) {
        BpmnModelInstance bpmnModelInstance = BpmnBuilder.build(processJson, versionTag);
        Deployment deployment = repositoryService.createDeployment()
                .name(processName)
                .tenantId(LoginHelper.getTenantId())
                .addModelInstance(processId + ".bpmn", bpmnModelInstance)
                .deploy();
        return deployment;
    }

    @Override
    public Page<ProcessDefinition> processDefinitionList(ProcessDefinitionDto processDefinitionDto, Page page) {
        int pageNum = page.getPageNum();
        int pageSize = page.getPageSize();
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery().latestVersion().tenantIdIn(LoginHelper.getTenantId());
        if (StrUtil.isNotEmpty(processDefinitionDto.getProcessName())) {
            processDefinitionQuery = processDefinitionQuery.processDefinitionNameLike("%" + processDefinitionDto.getProcessName() + "%");
        }
        if (StrUtil.isNotEmpty(processDefinitionDto.getVersionTag())) {
            processDefinitionQuery = processDefinitionQuery.versionTagLike("%" + processDefinitionDto.getVersionTag() + "%");
        }
        if (page.getTotal() == 0) {
            long count = processDefinitionQuery.count();
            page.setTotal(count);
        }
        List<ProcessDefinition> processDefinitionList = processDefinitionQuery.orderByDeploymentTime().desc().listPage((pageNum - 1) * pageSize, pageSize);
        page.setRows(processDefinitionList);
        return page;
    }

    @Override
    public List<ProcessDefinition> processDefinitionListByKey(String processDefinitionKey) {
        return repositoryService.createProcessDefinitionQuery().tenantIdIn(LoginHelper.getTenantId()).processDefinitionKey(processDefinitionKey).orderByDeploymentTime().desc().list();
    }

    @Override
    public void suspendProcessDefinitionById(String processDefinitionId) {
        repositoryService.suspendProcessDefinitionById(processDefinitionId);
    }

    @Override
    public void activateProcessDefinitionById(String processDefinitionId) {
        repositoryService.activateProcessDefinitionById(processDefinitionId);
    }

    @Override
    public void start(String processDefinitionId, Map<String, Object> formData) {
        String userId = String.valueOf(LoginHelper.getUserId());
        formData.put(ExtBpmConstant.ASSIGNEE, userId);
        identityService.setAuthentication(userId, null, List.of(String.valueOf(LoginHelper.getTenantId())));
        runtimeService.startProcessInstanceById(processDefinitionId, formData);
    }

    @Override
    public void approve(String taskId, Map<String, Object> formData, String userId) {
        taskService.claim(taskId, userId);
        taskService.complete(taskId, formData);
    }

    @Override
    public void reject(String taskId) {
        HistoricTaskInstance taskInstance = historyService.createHistoricTaskInstanceQuery()
                .taskId(taskId)
                .tenantIdIn(LoginHelper.getTenantId())
                .singleResult();
        // 获取流程定义
        ProcessDefinitionEntity processDefinitionEntity = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService).getDeployedProcessDefinition(taskInstance.getProcessDefinitionId());

        // 获取当前活动
        ActivityImpl currentActivity = processDefinitionEntity.findActivity(taskInstance.getTaskDefinitionKey());
        // 获取起始活动
//        List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery()
//                .activityType(BpmnElementType.USER_TASK.getElementTypeName().get())
//                .processInstanceId(taskInstance.getProcessInstanceId())
//                .finished()
//                .orderByHistoricActivityInstanceEndTime()
//                .asc()
//                .list();
//        String activityId = currentActivity.getActivityId();
//        for (int i = 0; i < historicActivityInstances.size(); i++) {
//            HistoricActivityInstance activityInstance = historicActivityInstances.get(i);
//            if (activityInstance.getActivityId().equals(activityId)) {
//                if (i == 0) {
//                    resolveTask(taskId);
//                } else {
//                    ActivityImpl lastActivity = processDefinitionEntity.findActivity(historicActivityInstances.get(i - 1).getActivityId());
//                    // 退回至上一级
//                    runtimeService.createProcessInstanceModification(taskInstance.getProcessInstanceId())
//                            .cancelAllForActivity(currentActivity.getActivityId())
//                            .startBeforeActivity(lastActivity.getActivityId())
//                            .execute();
//                    break;
//                }
//            }
//        }

//        HistoricActivityInstance historicActivityInstance = historyService.createHistoricActivityInstanceQuery()
//                .activityType(BpmnElementType.START_EVENT.getElementTypeName().get())
//                .processInstanceId(taskInstance.getProcessInstanceId())
//                .finished().singleResult();
//        ActivityImpl lastActivity = processDefinitionEntity.findActivity(historicActivityInstance.getActivityId());
//        // 退回发起人
//        runtimeService.createProcessInstanceModification(taskInstance.getProcessInstanceId())
//                .cancelAllForActivity(currentActivity.getActivityId())
//                .startBeforeActivity(lastActivity.getActivityId())
//                .execute();
        BpmnModelInstance bpmnModelInstance = repositoryService.getBpmnModelInstance(taskInstance.getProcessDefinitionId());
        List<EndEvent> endEvents = (List<EndEvent>) bpmnModelInstance.getModelElementsByType(EndEvent.class);
        EndEvent endEvent = endEvents.get(0);
        runtimeService.setVariable(taskInstance.getProcessInstanceId(), ExtBpmConstant.PROCESS_STATUS, ProcessStatus.TERMINATED.name());
        // 结束任务
        runtimeService.createProcessInstanceModification(taskInstance.getProcessInstanceId())
                .cancelAllForActivity(currentActivity.getActivityId())
                .startBeforeActivity(endEvent.getId())
                .execute();
    }

    @Override
    public void delegate(String taskId, String userId) {
        taskService.delegateTask(taskId, userId);
    }

    @Override
    public void resolveTask(String taskId, Map<String, Object> formData) {
        taskService.resolveTask(taskId, formData);
    }

    @Override
    public void revoke(String taskId) {
        HistoricTaskInstance task = historyService.createHistoricTaskInstanceQuery().tenantIdIn(LoginHelper.getTenantId()).taskId(taskId).singleResult();
        runtimeService.deleteProcessInstance(task.getProcessInstanceId(), "撤销");
        historyService.deleteHistoricProcessInstance(task.getProcessInstanceId());
    }

    @Override
    public void transfer(String taskId, String userId) {
        taskService.setAssignee(taskId, userId);
    }

    @Override
    public Page<Task> todoList(List<String> candidateList, String taskName, Date startDate, Date endDate, Page page) {
        int pageNum = page.getPageNum();
        int pageSize = page.getPageSize();
        TaskQuery taskQuery = taskService.createTaskQuery().tenantIdIn(LoginHelper.getTenantId()).or().taskCandidateGroupIn(candidateList).taskAssignee(String.valueOf(LoginHelper.getUserId())).endOr();
        if (StrUtil.isNotEmpty(taskName)) {
            taskQuery = taskQuery.taskNameLike("%" + taskName + "%");
        }
        if (startDate != null) {
            taskQuery = taskQuery.taskCreatedAfter(startDate);
        }
        if (endDate != null) {
            taskQuery = taskQuery.taskCreatedBefore(endDate);
        }
        if (page.getTotal() == 0) {
            long count = taskQuery.count();
            page.setTotal(count);
        }
        List<Task> taskList = taskQuery.orderByTaskCreateTime().desc().listPage((pageNum - 1) * pageSize, pageSize);
        page.setRows(taskList);
        return page;
    }

    @Override
    public Page<HistoricTaskInstance> todoneList(String userId, String taskName, Date startDate, Date endDate, Page page) {
        int pageNum = page.getPageNum();
        int pageSize = page.getPageSize();
        HistoricTaskInstanceQuery historicTaskInstanceQuery = historyService.createHistoricTaskInstanceQuery()
                .tenantIdIn(LoginHelper.getTenantId())
                .taskAssignee(userId)
                .finished();
        if (StrUtil.isNotEmpty(taskName)) {
            historicTaskInstanceQuery = historicTaskInstanceQuery.taskNameLike("%" + taskName + "%");
        }
        if (startDate != null) {
            historicTaskInstanceQuery = historicTaskInstanceQuery.finishedAfter(startDate);
        }
        if (endDate != null) {
            historicTaskInstanceQuery = historicTaskInstanceQuery.finishedBefore(endDate);
        }
        if (page.getTotal() == 0) {
            long count = historicTaskInstanceQuery.count();
            page.setTotal(count);
        }
        List<HistoricTaskInstance> historicTaskInstanceList = historicTaskInstanceQuery
                .orderByHistoricActivityInstanceStartTime().desc()
                .listPage((pageNum - 1) * pageSize, pageSize);
        page.setRows(historicTaskInstanceList);
        return page;
    }

    @Override
    public Page<HistoricProcessInstance> applyList(String userId, String processName, Date startDate, Date endDate, Page page) {
        int pageNum = page.getPageNum();
        int pageSize = page.getPageSize();
        HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery()
                .tenantIdIn(LoginHelper.getTenantId())
                .startedBy(userId);
        if (StrUtil.isNotEmpty(processName)) {
            historicProcessInstanceQuery = historicProcessInstanceQuery.processDefinitionNameLike("%" + processName + "%");
        }
        if (startDate != null) {
            historicProcessInstanceQuery = historicProcessInstanceQuery.startedAfter(startDate);
        }
        if (endDate != null) {
            historicProcessInstanceQuery = historicProcessInstanceQuery.startedBefore(endDate);
        }
        if (page.getTotal() == 0) {
            long count = historicProcessInstanceQuery.count();
            page.setTotal(count);
        }
        List<HistoricProcessInstance> historicProcessInstances = historicProcessInstanceQuery
                .orderByProcessInstanceStartTime().desc()
                .listPage((pageNum - 1) * pageSize, pageSize);
        page.setRows(historicProcessInstances);
        return page;
    }

    @Override
    public BpmnModelInstance bpmnModelInstance(String processDefinitionId) {
        return repositoryService.getBpmnModelInstance(processDefinitionId);
    }

    @Override
    public ProcessDefinition processDefinition(String processDefinitionId) {
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().tenantIdIn(LoginHelper.getTenantId()).processDefinitionId(processDefinitionId).singleResult();
        return processDefinition;
    }

    @Override
    public HistoricProcessInstance historicProcessInstance(String processInstanceId) {
        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                .tenantIdIn(LoginHelper.getTenantId())
                .processInstanceId(processInstanceId)
                .singleResult();
        return historicProcessInstance;
    }

    @Override
    public Task task(String taskId) {
        Task task = taskService.createTaskQuery().tenantIdIn(LoginHelper.getTenantId()).taskId(taskId).singleResult();
        return task;
    }

    @Override
    public FlowElement flowElement(String processDefinitionId, String nodeKey) {
        BpmnModelInstance bpmnModelInstance = repositoryService.getBpmnModelInstance(processDefinitionId);
        Collection<Process> processList = bpmnModelInstance.getModelElementsByType(Process.class);
        for (Process process : processList) {
            Collection<FlowElement> flowElementList = process.getFlowElements();
            for (FlowElement flowElement : flowElementList) {
                if (StrUtil.isEmpty(nodeKey) || nodeKey.equals(flowElement.getId())) {
                    return flowElement;
                }
            }
        }
        return null;
    }

    @Override
    public Map<String, Object> formData(String processInstanceId) {
        List<HistoricVariableInstance> variableInstanceList = historyService.createHistoricVariableInstanceQuery().tenantIdIn(LoginHelper.getTenantId()).processInstanceId(processInstanceId).list();
        Map<String, Object> formData = variableInstanceList
                .stream()
                .collect(Collectors.toMap(HistoricVariableInstance::getName, HistoricVariableInstance::getValue));
        return formData;
    }

    @Override
    public Object getVariable(String processInstanceId, String variableName) {
        HistoricVariableInstance historicVariableInstance = historyService.createHistoricVariableInstanceQuery().tenantIdIn(LoginHelper.getTenantId()).processInstanceId(processInstanceId).variableName(variableName).singleResult();
        return historicVariableInstance.getValue();
    }

    @Override
    public void deleteDeployment(String deploymentId) {
        repositoryService.deleteDeployment(deploymentId);
    }

    @Override
    public Deployment deployment(String deploymentId) {
        Deployment deployment = repositoryService.createDeploymentQuery().tenantIdIn(LoginHelper.getTenantId()).deploymentId(deploymentId).singleResult();
        return deployment;
    }

    @Override
    public List<HistoricActivityInstance> historicActivityInstanceList(String processInstanceId) {
        List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery()
                .tenantIdIn(LoginHelper.getTenantId())
                .processInstanceId(processInstanceId)
                .orderByHistoricActivityInstanceStartTime().desc()
                .orderByHistoricActivityInstanceEndTime().desc()
                .list();
        return historicActivityInstanceList;
    }
}
